{ "cells": [ { "cell_type": "markdown", "id": "89cf2628", "metadata": {}, "source": [ "# Validation Examples\n", "\n", "As a result of optimizations, and the low level nature of the Power Grid Model's mathematical core, the core exceptions may not always be clear to the user. Therefore an optional validation mechanism is supplied, which validates data structures and values off-line. It is recommended to always validate your data before constructing a PowerGridModel instance. An alternative approach would be to validate only when an exception is raised, but be aware that not all data errors will raise exceptions: most of them will just yield invalid results without warning." ] }, { "cell_type": "markdown", "id": "3cbdd654", "metadata": {}, "source": [ "The basic methods and class definitions are available in the `power_grid_model.validation` module:\n", "\n", "```python\n", "# Manual validation\n", "# validate_input_data() assumes that you won't be using update data in your calculation.\n", "# validate_batch_data() validates input_data in combination with batch/update data.\n", "validate_input_data(input_data, calculation_type, symmetric) -> list[ValidationError]\n", "validate_batch_data(input_data, update_data, calculation_type, symmetric) -> dict[int, list[ValidationError]]\n", "\n", "# Assertions\n", "# assert_valid_input_data() and assert_valid_batch_data() raise a ValidationException,\n", "# containing the list/dict of errors, when the data is invalid.\n", "assert_valid_input_data(input_data, calculation_type, symmetric) raises ValidationException\n", "assert_valid_batch_data(input_data, calculation_type, update_data, symmetric) raises ValidationException\n", "\n", "# Utilities\n", "# errors_to_string() converts a set of errors to a human readable (multi-line) string representation\n", "errors_to_string(errors, name, details)\n", "```\n", "\n", "Each validation error is an object which can be converted to a compact human-readable message using `str(error)`. It\n", "contains three member variables `component`, `field` and `ids`, which can be used to gather more specific information about the validation error, e.g. which object IDs are involved.\n", "\n", "```python\n", "class ValidationError:\n", " \n", " # Component(s): e.g. ComponentType.node or [ComponentType.node, ComponentType.line]\n", " component: ComponentType | list[ComponentType]\n", " \n", " # Field(s): e.g. \"id\" or [\"line_from\", \"line_to\"] or [(ComponentType.node, \"id\"), (ComponentType.line, \"id\")]\n", " field: str | list[str] | list[tuple[ComponentType, str]]\n", "\n", " # IDs: e.g. [1, 2, 3] or [(ComponentType.node, 1), (ComponentType.line, 1)]\n", " ids: list[int] | list[tuple[ComponentType, int]] = [] \n", " \n", "```\n", "\n", "Note: The data types of `input_data` and `update_data` are the same as expected by the power grid model." ] }, { "cell_type": "code", "execution_count": 1, "id": "d122ee22", "metadata": {}, "outputs": [], "source": [ "from power_grid_model import ComponentType, DatasetType, PowerGridModel, initialize_array\n", "\n", "# A power grid containing several errors\n", "\n", "# node\n", "node_error = initialize_array(DatasetType.input, ComponentType.node, 3)\n", "node_error[\"id\"] = [1, 2, 3]\n", "node_error[\"u_rated\"] = [10.5e3]\n", "\n", "# line\n", "line_error = initialize_array(DatasetType.input, ComponentType.line, 3)\n", "line_error[\"id\"] = [4, 5, 6]\n", "line_error[\"from_node\"] = [1, 2, 3]\n", "line_error[\"to_node\"] = [2, 3, 4]\n", "line_error[\"from_status\"] = [True]\n", "line_error[\"to_status\"] = [True]\n", "line_error[\"r1\"] = [0.25]\n", "line_error[\"x1\"] = [0.2]\n", "line_error[\"c1\"] = [10e-6]\n", "line_error[\"tan1\"] = [0.0]\n", "\n", "# Power Sensor\n", "sensor_error = initialize_array(DatasetType.input, ComponentType.sym_power_sensor, 2)\n", "sensor_error[\"id\"] = [6, 7]\n", "sensor_error[\"measured_object\"] = [3, 4]\n", "sensor_error[\"measured_terminal_type\"] = [0, 2]\n", "sensor_error[\"p_measured\"] = [0]\n", "sensor_error[\"q_measured\"] = [0]\n", "sensor_error[\"power_sigma\"] = [0]\n", "\n", "error_data = {\n", " ComponentType.node: node_error,\n", " ComponentType.line: line_error,\n", " ComponentType.sym_power_sensor: sensor_error,\n", "}" ] }, { "cell_type": "code", "execution_count": 2, "id": "d997e738", "metadata": {}, "outputs": [ { "ename": "IDWrongType", "evalue": "Wrong type for object with id 4\n\nTry validate_input_data() or validate_batch_data() to validate your data.\n", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mIDWrongType\u001b[39m Traceback (most recent call last)", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;66;03m# Without validation\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m model = \u001b[43mPowerGridModel\u001b[49m\u001b[43m(\u001b[49m\u001b[43merror_data\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 3\u001b[39m output_data = model.calculate_state_estimation(symmetric=\u001b[38;5;28;01mTrue\u001b[39;00m)\n", "\u001b[36mFile \u001b[39m\u001b[32m~\\power-grid-model\\src\\power_grid_model\\_core\\power_grid_model.py:125\u001b[39m, in \u001b[36mPowerGridModel.__init__\u001b[39m\u001b[34m(self, input_data, system_frequency)\u001b[39m\n\u001b[32m 123\u001b[39m prepared_input = prepare_input_view(_map_to_component_types(input_data))\n\u001b[32m 124\u001b[39m \u001b[38;5;28mself\u001b[39m._model_ptr = pgc.create_model(system_frequency, input_data=prepared_input.get_dataset_ptr())\n\u001b[32m--> \u001b[39m\u001b[32m125\u001b[39m \u001b[43massert_no_error\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 126\u001b[39m \u001b[38;5;28mself\u001b[39m._all_component_count = {k: v \u001b[38;5;28;01mfor\u001b[39;00m k, v \u001b[38;5;129;01min\u001b[39;00m prepared_input.get_info().total_elements().items() \u001b[38;5;28;01mif\u001b[39;00m v > \u001b[32m0\u001b[39m}\n", "\u001b[36mFile \u001b[39m\u001b[32m~\\power-grid-model\\src\\power_grid_model\\_core\\error_handling.py:175\u001b[39m, in \u001b[36massert_no_error\u001b[39m\u001b[34m(batch_size, decode_error)\u001b[39m\n\u001b[32m 173\u001b[39m error = find_error(batch_size=batch_size, decode_error=decode_error)\n\u001b[32m 174\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m error \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m175\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m error\n", "\u001b[31mIDWrongType\u001b[39m: Wrong type for object with id 4\n\nTry validate_input_data() or validate_batch_data() to validate your data.\n" ] } ], "source": [ "# Without validation\n", "model = PowerGridModel(error_data)\n", "output_data = model.calculate_state_estimation(symmetric=True)" ] }, { "cell_type": "code", "execution_count": 3, "id": "fd84be3c", "metadata": {}, "outputs": [ { "ename": "ValidationException", "evalue": "There are 5 validation errors in input_data:\n 1. Fields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n 2. Field 'to_node' does not contain a valid node id for 1 line.\n 3. Field 'power_sigma' is not greater than zero for 2 sym_power_sensors.\n 4. Field 'measured_object' does not contain a valid line/asym_line/generic_branch/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n 5. Field 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mValidationException\u001b[39m Traceback (most recent call last)", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[3]\u001b[39m\u001b[32m, line 4\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mpower_grid_model\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mvalidation\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m assert_valid_input_data\n\u001b[32m 3\u001b[39m \u001b[38;5;66;03m# Assert valid data\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m4\u001b[39m \u001b[43massert_valid_input_data\u001b[49m\u001b[43m(\u001b[49m\u001b[43merror_data\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msymmetric\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[32m 5\u001b[39m model = PowerGridModel(error_data)\n\u001b[32m 6\u001b[39m output_data = model.calculate_state_estimation(symmetric=\u001b[38;5;28;01mTrue\u001b[39;00m)\n", "\u001b[36mFile \u001b[39m\u001b[32m~\\power-grid-model\\src\\power_grid_model\\validation\\assertions.py:55\u001b[39m, in \u001b[36massert_valid_input_data\u001b[39m\u001b[34m(input_data, calculation_type, symmetric)\u001b[39m\n\u001b[32m 51\u001b[39m validation_errors = validate_input_data(\n\u001b[32m 52\u001b[39m input_data=input_data, calculation_type=calculation_type, symmetric=symmetric\n\u001b[32m 53\u001b[39m )\n\u001b[32m 54\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m validation_errors:\n\u001b[32m---> \u001b[39m\u001b[32m55\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m ValidationException(validation_errors, \u001b[33m\"\u001b[39m\u001b[33minput_data\u001b[39m\u001b[33m\"\u001b[39m)\n", "\u001b[31mValidationException\u001b[39m: There are 5 validation errors in input_data:\n 1. Fields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n 2. Field 'to_node' does not contain a valid node id for 1 line.\n 3. Field 'power_sigma' is not greater than zero for 2 sym_power_sensors.\n 4. Field 'measured_object' does not contain a valid line/asym_line/generic_branch/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n 5. Field 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)" ] } ], "source": [ "from power_grid_model.validation import assert_valid_input_data\n", "\n", "# Assert valid data\n", "assert_valid_input_data(error_data, symmetric=True)\n", "model = PowerGridModel(error_data)\n", "output_data = model.calculate_state_estimation(symmetric=True)" ] }, { "cell_type": "code", "execution_count": 4, "id": "6286ece0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MultiComponentNotUniqueError [, ] : [(, np.int32(6)), (, np.int32(6))]\n", "InvalidIdError ComponentType.line : [6]\n", "NotGreaterThanError ComponentType.sym_power_sensor : [6, 7]\n", "InvalidIdError ComponentType.sym_power_sensor : [6]\n", "InvalidIdError ComponentType.sym_power_sensor : [7]\n" ] } ], "source": [ "from power_grid_model.validation import ValidationException\n", "\n", "# Assert valid data and display component ids\n", "try:\n", " assert_valid_input_data(error_data, symmetric=True)\n", " model = PowerGridModel(error_data)\n", " output_data = model.calculate_state_estimation(symmetric=True)\n", "except ValidationException as ex:\n", " for error in ex.errors:\n", " print(type(error).__name__, error.component, \":\", error.ids)" ] }, { "cell_type": "code", "execution_count": 5, "id": "1e4ca721", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "There are 5 validation errors in the data:\n", " 1. Fields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n", " 2. Field 'to_node' does not contain a valid node id for 1 line.\n", " 3. Field 'power_sigma' is not greater than zero for 2 sym_power_sensors.\n", " 4. Field 'measured_object' does not contain a valid line/asym_line/generic_branch/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n", " 5. Field 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)\n" ] } ], "source": [ "from power_grid_model.validation import errors_to_string, validate_input_data\n", "\n", "# Validation only as exception handling\n", "try:\n", " model = PowerGridModel(error_data)\n", " output_data = model.calculate_state_estimation(symmetric=True)\n", "except RuntimeError as _ex:\n", " errors = validate_input_data(error_data, symmetric=True)\n", " print(errors_to_string(errors))" ] }, { "cell_type": "code", "execution_count": 6, "id": "91a1aff6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "There are 5 validation errors in the data:\n", "\n", "\tFields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n", "\t\tcomponent: line/sym_power_sensor\n", "\t\tfield: line.id and sym_power_sensor.id\n", "\t\tids: [(, np.int32(6)), (, np.int32(6))]\n", "\n", "\tField 'to_node' does not contain a valid node id for 1 line.\n", "\t\tcomponent: line\n", "\t\tfield: 'to_node'\n", "\t\tids: [6]\n", "\t\tref_components: node\n", "\t\tfilters: \n", "\n", "\tField 'power_sigma' is not greater than zero for 2 sym_power_sensors.\n", "\t\tcomponent: sym_power_sensor\n", "\t\tfield: 'power_sigma'\n", "\t\tids: [6, 7]\n", "\t\tref_value: zero\n", "\n", "\tField 'measured_object' does not contain a valid line/asym_line/generic_branch/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n", "\t\tcomponent: sym_power_sensor\n", "\t\tfield: 'measured_object'\n", "\t\tids: [6]\n", "\t\tref_components: line/asym_line/generic_branch/transformer\n", "\t\tfilters: (measured_terminal_type=branch_from)\n", "\n", "\tField 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)\n", "\t\tcomponent: sym_power_sensor\n", "\t\tfield: 'measured_object'\n", "\t\tids: [7]\n", "\t\tref_components: source\n", "\t\tfilters: (measured_terminal_type=source)\n", "\n" ] } ], "source": [ "# Manual checking and display detailed information about the invalid data\n", "errors = validate_input_data(error_data, symmetric=True)\n", "print(errors_to_string(errors, details=True))" ] } ], "metadata": { "kernelspec": { "display_name": "venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.3" } }, "nbformat": 4, "nbformat_minor": 5 }